// element-plus click-outside
// 判断更多，考虑的场景更完全

// 收集使用了指令的node，触发指令时遍历执行
const nodeList = new Map()

// 记录 mousedown 的事件对象 event
let startClick

// 监听鼠标/触控板事件，左中右三键都可以触发，而click只能由左键触发
document.addEventListener('mousedown', e => (startClick = e))
document.addEventListener('mouseup', e => {
  for (const handlers of nodeList.values()) {
    for (const { documentHandler } of handlers) {
      documentHandler(e, startClick)
    }
  }
})

function createDocumentHandler(el, binding) {
  let excludes = []
  if (Array.isArray(binding.arg)) {
    excludes = binding.arg
  } else {
    // due to current implementation on binding type is wrong the type casting is necessary here
    excludes.push(binding.arg)
  }
  return function (mouseup, mousedown) {
    const popperRef = binding.instance.popperRef
    const mouseUpTarget = mouseup.target
    const mouseDownTarget = mousedown?.target
    // 使用指令的目标元素不存在
    const isBound = !binding || !binding.instance
    // 点击的目标元素不存在
    const isTargetExists = !mouseUpTarget || !mouseDownTarget
    // 点击目标在指令目标内
    const isContainedByEl =
      el.contains(mouseUpTarget) || el.contains(mouseDownTarget)
    // 点击目标是指令目标
    const isSelf = el === mouseUpTarget
    // 点击目标是在额外能触发指令的元素或子元素内
    const isTargetExcluded =
      (excludes.length &&
        excludes.some(item => item?.contains(mouseUpTarget))) ||
      (excludes.length && excludes.includes(mouseDownTarget))
    // 点击目标是指令目标的 popperRef 元素内
    const isContainedByPopper =
      popperRef &&
      (popperRef.contains(mouseUpTarget) || popperRef.contains(mouseDownTarget))
    // 以上判断都不会触发 clickoutside 事件
    if (
      isBound ||
      isTargetExists ||
      isContainedByEl ||
      isSelf ||
      isTargetExcluded ||
      isContainedByPopper
    ) {
      return
    }
    binding.value(mouseup, mousedown)
  }
}

const vClickOutside = {
  beforeMount(el, binding) {
    // there could be multiple handlers on the element
    if (!nodeList.has(el)) {
      nodeList.set(el, [])
    }

    nodeList.get(el).push({
      documentHandler: createDocumentHandler(el, binding),
      bindingFn: binding.value,
    })
  },
  updated(el, binding) {
    if (!nodeList.has(el)) {
      nodeList.set(el, [])
    }

    const handlers = nodeList.get(el)
    const oldHandlerIndex = handlers.findIndex(
      item => item.bindingFn === binding.oldValue,
    )
    const newHandler = {
      documentHandler: createDocumentHandler(el, binding),
      bindingFn: binding.value,
    }

    if (oldHandlerIndex >= 0) {
      // replace the old handler to the new handler
      handlers.splice(oldHandlerIndex, 1, newHandler)
    } else {
      handlers.push(newHandler)
    }
  },
  unmounted(el) {
    // remove all listeners when a component unmounted
    nodeList.delete(el)
  },
}

export { vClickOutside }
